﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.templates;
using l1cs.server.datatables;
using l1cs.server.serverpackets;
using l1cs.server.model.Instance;
using l1cs.server.model.skill;
using System.Collections;

namespace l1cs.server.model
{
    class L1MobSkillUse
    {
        private static Logger _log = Logger.getLogger( typeof( L1MobSkillUse ).FullName );

        private L1MobSkill _mobSkillTemplate = null;

        private L1NpcInstance _attacker = null;

        private L1Character _target = null;

        private Random _rnd = new Random();

        private int _sleepTime = 0;

        private int[] _skillUseCount;

        public L1MobSkillUse( L1NpcInstance npc )
        {
            _sleepTime = 0;

            _mobSkillTemplate = MobSkillTable.getInstance().getTemplate(
                    npc.getNpcTemplate().get_npcId() );
            if ( _mobSkillTemplate == null )
            {
                return;
            }
            _attacker = npc;
            _skillUseCount = new int[getMobSkillTemplate().getSkillSize()];
        }

        private int getSkillUseCount( int idx )
        {
            return _skillUseCount[idx];
        }

        private void skillUseCountUp( int idx )
        {
            _skillUseCount[idx]++;
        }

        public void resetAllSkillUseCount()
        {
            if ( getMobSkillTemplate() == null )
            {
                return;
            }

            for ( int i = 0; i < getMobSkillTemplate().getSkillSize(); i++ )
            {
                _skillUseCount[i] = 0;
            }
        }

        public int getSleepTime()
        {
            return _sleepTime;
        }

        public void setSleepTime( int i )
        {
            _sleepTime = i;
        }

        public L1MobSkill getMobSkillTemplate()
        {
            return _mobSkillTemplate;
        }

        /// <summary>
        /// トリガーの条件のみチェック。
        /// </summary>
        /// <param name="tg"></param>
        /// <returns></returns>
        public bool isSkillTrigger( L1Character tg )
        {
            if ( _mobSkillTemplate == null )
            {
                return false;
            }
            _target = tg;

            int type;
            type = getMobSkillTemplate().getType( 0 );

            if ( type == L1MobSkill.TYPE_NONE )
            {
                return false;
            }

            int i = 0;
            for ( i = 0; i < getMobSkillTemplate().getSkillSize()
                    && getMobSkillTemplate().getType( i ) != L1MobSkill.TYPE_NONE; i++ )
            {

                // changeTargetが設定されている場合、ターゲットの入れ替え
                int changeType = getMobSkillTemplate().getChangeTarget( i );
                if ( changeType > 0 )
                {
                    _target = changeTarget( changeType, i );
                }
                else
                {
                    // 設定されてない場合は本来のターゲットにする
                    _target = tg;
                }

                if ( isSkillUseble( i, false ) )
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// スキル攻撃 スキル攻撃可能ならばtrueを返す。 攻撃できなければfalseを返す。
        /// </summary>
        /// <param name="tg"></param>
        /// <param name="isTriRnd"></param>
        /// <returns></returns>
        public bool skillUse( L1Character tg, bool isTriRnd )
        {
            if ( _mobSkillTemplate == null )
            {
                return false;
            }
            _target = tg;

            int type;
            type = getMobSkillTemplate().getType( 0 );

            if ( type == L1MobSkill.TYPE_NONE )
            {
                return false;
            }

            int[] skills = null;
            int skillSizeCounter = 0;
            int skillSize = getMobSkillTemplate().getSkillSize();
            if ( skillSize >= 0 )
            {
                skills = new int[skillSize];
            }

            int i = 0;
            for ( i = 0; i < getMobSkillTemplate().getSkillSize()
                    && getMobSkillTemplate().getType( i ) != L1MobSkill.TYPE_NONE; i++ )
            {

                // changeTargetが設定されている場合、ターゲットの入れ替え
                int changeType = getMobSkillTemplate().getChangeTarget( i );
                if ( changeType > 0 )
                {
                    _target = changeTarget( changeType, i );
                }
                else
                {
                    // 設定されてない場合は本来のターゲットにする
                    _target = tg;
                }

                if ( isSkillUseble( i, isTriRnd ) == false )
                {
                    continue;
                }
                else
                { // 条件にあうスキルが存在する
                    skills[skillSizeCounter] = i;
                    skillSizeCounter++;
                }
            }

            if ( skillSizeCounter != 0 )
            {
                int num = _rnd.Next( skillSizeCounter );
                if ( useSkill( skills[num] ) )
                { // スキル使用
                    return true;
                }
            }

            return false;
        }

        private bool useSkill( int i )
        {
            bool isUseSkill = false;
            int type = getMobSkillTemplate().getType( i );
            if ( type == L1MobSkill.TYPE_PHYSICAL_ATTACK )
            { // 物理攻撃
                if ( physicalAttack( i ) == true )
                {
                    skillUseCountUp( i );
                    isUseSkill = true;
                }
            }
            else if ( type == L1MobSkill.TYPE_MAGIC_ATTACK )
            { // 魔法攻撃
                if ( magicAttack( i ) == true )
                {
                    skillUseCountUp( i );
                    isUseSkill = true;
                }
            }
            else if ( type == L1MobSkill.TYPE_SUMMON )
            { // サモンする
                if ( summon( i ) == true )
                {
                    skillUseCountUp( i );
                    isUseSkill = true;
                }
            }
            else if ( type == L1MobSkill.TYPE_POLY )
            { // 強制変身させる
                if ( poly( i ) == true )
                {
                    skillUseCountUp( i );
                    isUseSkill = true;
                }
            }
            return isUseSkill;
        }

        private bool summon( int idx )
        {
            int summonId = getMobSkillTemplate().getSummon( idx );
            int min = getMobSkillTemplate().getSummonMin( idx );
            int max = getMobSkillTemplate().getSummonMax( idx );
            int count = 0;

            if ( summonId == 0 )
            {
                return false;
            }

            count = _rnd.Next( max ) + min;
            mobspawn( summonId, count );

            // 魔方陣の表示
            _attacker.broadcastPacket( new S_SkillSound( _attacker.getId(), 761 ) );

            // 魔法を使う動作のエフェクト
            S_DoActionGFX gfx = new S_DoActionGFX( _attacker.getId(),
                    ActionCodes.ACTION_SkillBuff );
            _attacker.broadcastPacket( gfx );

            _sleepTime = _attacker.getNpcTemplate().getSubMagicSpeed();
            return true;
        }

        /// <summary>
        /// 15セル以内で射線が通るPCを指定したモンスターに強制変身させる。 対PCしか使えない。
        /// </summary>
        /// <param name="idx"></param>
        /// <returns></returns>
        private bool poly( int idx )
        {
            int polyId = getMobSkillTemplate().getPolyId( idx );
            bool usePoly = false;

            if ( polyId == 0 )
            {
                return false;
            }

            foreach ( L1PcInstance pc in L1World.getInstance().getVisiblePlayer( _attacker ) )
            {
                if ( pc.isDead() )
                { // 死亡している
                    continue;
                }
                if ( pc.isGhost() )
                {
                    continue;
                }
                if ( pc.isGmInvis() )
                {
                    continue;
                }
                if ( _attacker.glanceCheck( pc.getX(), pc.getY() ) == false )
                {
                    continue; // 射線が通らない
                }

                int npcId = _attacker.getNpcTemplate().get_npcId();
                switch ( npcId )
                {
                    case 81082: // ヤヒの場合
                        pc.getInventory().takeoffEquip( 945 ); // 牛のpolyIdで装備を全部外す。
                        break;
                    default:
                        break;
                }
                L1PolyMorph.doPoly( pc, polyId, 1800, L1PolyMorph.MORPH_BY_NPC );

                usePoly = true;
            }
            if ( usePoly )
            {
                // 変身させた場合、オレンジの柱を表示する。
                foreach ( L1PcInstance pc in L1World.getInstance()
                        .getVisiblePlayer( _attacker ) )
                {
                    pc.sendPackets( new S_SkillSound( pc.getId(), 230 ) );
                    pc.broadcastPacket( new S_SkillSound( pc.getId(), 230 ) );
                    break;
                }
                // 魔法を使う動作のエフェクト
                S_DoActionGFX gfx = new S_DoActionGFX( _attacker.getId(),
                        ActionCodes.ACTION_SkillBuff );
                _attacker.broadcastPacket( gfx );

                _sleepTime = _attacker.getNpcTemplate().getSubMagicSpeed();
            }

            return usePoly;
        }

        private bool magicAttack( int idx )
        {
            L1SkillUse skillUse = new L1SkillUse();
            int skillid = getMobSkillTemplate().getSkillId( idx );
            bool canUseSkill = false;

            if ( skillid > 0 )
            {
                canUseSkill = skillUse.checkUseSkill( null, skillid,
                        _target.getId(), _target.getX(), _target.getY(), null, 0,
                        L1SkillUse.TYPE_NORMAL, _attacker );
            }

            if ( canUseSkill == true )
            {
                if ( getMobSkillTemplate().getLeverage( idx ) > 0 )
                {
                    skillUse.setLeverage( getMobSkillTemplate().getLeverage( idx ) );
                }
                skillUse.handleCommands( null, skillid, _target.getId(), _target
                        .getX(), _target.getX(), null, 0, L1SkillUse.TYPE_NORMAL,
                        _attacker );
                // 使用スキルによるsleepTimeの設定
                L1Skills skill = SkillsTable.getInstance().getTemplate( skillid );
                if ( skill.getTarget().Equals( "attack" ) && skillid != 18 )
                { // 有方向魔法
                    _sleepTime = _attacker.getNpcTemplate().getAtkMagicSpeed();
                }
                else
                { // 無方向魔法
                    _sleepTime = _attacker.getNpcTemplate().getSubMagicSpeed();
                }

                return true;
            }
            return false;
        }

        /// <summary>
        /// 物理攻撃
        /// </summary>
        /// <param name="idx"></param>
        /// <returns></returns>
        private bool physicalAttack( int idx )
        {
            Hashtable targetList = Hashtable.Synchronized( new Hashtable() );
            int areaWidth = getMobSkillTemplate().getAreaWidth( idx );
            int areaHeight = getMobSkillTemplate().getAreaHeight( idx );
            int range = getMobSkillTemplate().getRange( idx );
            int actId = getMobSkillTemplate().getActid( idx );
            int gfxId = getMobSkillTemplate().getGfxid( idx );

            // レンジ外
            if ( _attacker.getLocation().getTileLineDistance( _target.getLocation() ) > range )
            {
                return false;
            }

            // 障害物がある場合攻撃不可能
            if ( !_attacker.glanceCheck( _target.getX(), _target.getY() ) )
            {
                return false;
            }

            _attacker.setHeading( _attacker.targetDirection( _target.getX(), _target
                    .getY() ) ); // 向きのセット

            if ( areaHeight > 0 )
            {
                // 範囲攻撃
                List<L1Object> objs = L1World.getInstance()
                        .getVisibleBoxObjects( _attacker, _attacker.getHeading(),
                                areaWidth, areaHeight );

                foreach ( L1Object obj in objs )
                {
                    if ( !( obj is L1Character ) )
                    { // ターゲットがキャラクター以外の場合何もしない。
                        continue;
                    }

                    L1Character cha = (L1Character)obj;
                    if ( cha.isDead() )
                    { // 死んでるキャラクターは対象外
                        continue;
                    }

                    // ゴースト状態は対象外
                    if ( cha is L1PcInstance )
                    {
                        if ( ( (L1PcInstance)cha ).isGhost() )
                        {
                            continue;
                        }
                    }

                    // 障害物がある場合は対象外
                    if ( !_attacker.glanceCheck( cha.getX(), cha.getY() ) )
                    {
                        continue;
                    }

                    if ( _target is L1PcInstance
                            || _target is L1SummonInstance
                            || _target is L1PetInstance )
                    {
                        // 対PC
                        if ( obj is L1PcInstance
                                && !( (L1PcInstance)obj ).isGhost()
                                && !( (L1PcInstance)obj ).isGmInvis()
                                || obj is L1SummonInstance
                                || obj is L1PetInstance )
                        {
                            targetList.Add( obj.getId(), 0 );
                        }
                    }
                    else
                    {
                        // 対NPC
                        if ( obj is L1MonsterInstance )
                        {
                            targetList.Add( obj.getId(), 0 );
                        }
                    }
                }
            }
            else
            {
                // 単体攻撃
                targetList.Add( _target.getId(), 0 ); // ターゲットのみ追加
            }

            if ( targetList.Count == 0 )
            {
                return false;
            }

            lock ( targetList )
            {
                foreach ( int targetId in targetList.Keys )
                {
                    L1Attack attack = new L1Attack( _attacker, (L1Character)L1World
                            .getInstance().findObject( targetId ) );
                    if ( attack.calcHit() )
                    {
                        if ( getMobSkillTemplate().getLeverage( idx ) > 0 )
                        {
                            attack.setLeverage( getMobSkillTemplate().getLeverage( idx ) );
                        }
                        attack.calcDamage();
                    }
                    if ( actId > 0 )
                    {
                        attack.setActId( actId );
                    }
                    // 攻撃モーションは実際のターゲットに対してのみ行う
                    if ( targetId == _target.getId() )
                    {
                        if ( gfxId > 0 )
                        {
                            _attacker.broadcastPacket( new S_SkillSound( _attacker
                                    .getId(), gfxId ) );
                        }
                        attack.action();
                    }
                    attack.commit();
                }
            }
            _sleepTime = _attacker.getAtkspeed();
            return true;
        }

        /// <summary>
        /// トリガーの条件のみチェック
        /// </summary>
        /// <param name="skillIdx"></param>
        /// <param name="isTriRnd"></param>
        /// <returns></returns>
        private bool isSkillUseble( int skillIdx, bool isTriRnd )
        {
            bool useble = false;
            int type = getMobSkillTemplate().getType( skillIdx );

            if ( isTriRnd || type == L1MobSkill.TYPE_SUMMON
                    || type == L1MobSkill.TYPE_POLY )
            {
                if ( getMobSkillTemplate().getTriggerRandom( skillIdx ) > 0 )
                {
                    int chance = _rnd.Next( 100 ) + 1;
                    if ( chance < getMobSkillTemplate().getTriggerRandom( skillIdx ) )
                    {
                        useble = true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            if ( getMobSkillTemplate().getTriggerHp( skillIdx ) > 0 )
            {
                int hpRatio = ( _attacker.getCurrentHp() * 100 )
                        / _attacker.getMaxHp();
                if ( hpRatio <= getMobSkillTemplate().getTriggerHp( skillIdx ) )
                {
                    useble = true;
                }
                else
                {
                    return false;
                }
            }

            if ( getMobSkillTemplate().getTriggerCompanionHp( skillIdx ) > 0 )
            {
                L1NpcInstance companionNpc = searchMinCompanionHp();
                if ( companionNpc == null )
                {
                    return false;
                }

                int hpRatio = ( companionNpc.getCurrentHp() * 100 )
                        / companionNpc.getMaxHp();
                if ( hpRatio <= getMobSkillTemplate()
                        .getTriggerCompanionHp( skillIdx ) )
                {
                    useble = true;
                    _target = companionNpc; // ターゲットの入れ替え
                }
                else
                {
                    return false;
                }
            }

            if ( getMobSkillTemplate().getTriggerRange( skillIdx ) != 0 )
            {
                int distance = _attacker.getLocation().getTileLineDistance(
                        _target.getLocation() );

                if ( getMobSkillTemplate().isTriggerDistance( skillIdx, distance ) )
                {
                    useble = true;
                }
                else
                {
                    return false;
                }
            }

            if ( getMobSkillTemplate().getTriggerCount( skillIdx ) > 0 )
            {
                if ( getSkillUseCount( skillIdx ) < getMobSkillTemplate()
                        .getTriggerCount( skillIdx ) )
                {
                    useble = true;
                }
                else
                {
                    return false;
                }
            }
            return useble;
        }

        private L1NpcInstance searchMinCompanionHp()
        {
            L1NpcInstance npc;
            L1NpcInstance minHpNpc = null;
            int hpRatio = 100;
            int companionHpRatio;
            int family = _attacker.getNpcTemplate().get_family();

            foreach ( L1Object l1object in L1World.getInstance().getVisibleObjects( _attacker ) )
            {
                if ( l1object is L1NpcInstance )
                {
                    npc = (L1NpcInstance)l1object;
                    if ( npc.getNpcTemplate().get_family() == family )
                    {
                        companionHpRatio = ( npc.getCurrentHp() * 100 )
                                / npc.getMaxHp();
                        if ( companionHpRatio < hpRatio )
                        {
                            hpRatio = companionHpRatio;
                            minHpNpc = npc;
                        }
                    }
                }
            }
            return minHpNpc;
        }

        private void mobspawn( int summonId, int count )
        {
            int i;

            for ( i = 0; i < count; i++ )
            {
                mobspawn( summonId );
            }
        }

        private void mobspawn( int summonId )
        {
            L1Npc spawnmonster = NpcTable.getInstance().getTemplate( summonId );
            if ( spawnmonster != null )
            {
                L1NpcInstance mob = null;
                String implementationName = spawnmonster.getImpl();
                //Constructor _constructor = Class.forName(
                //        (new StringBuilder()).append(
                //                "l1j.server.server.model.Instance.")
                //                .append(implementationName).append(
                //                        "Instance").toString())
                //        .getConstructors()[0];
                //mob = (L1NpcInstance) _constructor
                //        .newInstance(new Object[] { spawnmonster });
                mob = NpcTable.getInstance().newNpcInstance( spawnmonster );
                mob.setId( IdFactory.getInstance().nextId() );
                L1Location loc = _attacker.getLocation().randomLocation( 8,
                        false );
                int heading = _rnd.Next( 8 );
                mob.setX( loc.getX() );
                mob.setY( loc.getY() );
                mob.setHomeX( loc.getX() );
                mob.setHomeY( loc.getY() );
                short mapid = _attacker.getMapId();
                mob.setMap( mapid );
                mob.setHeading( heading );
                L1World.getInstance().storeObject( mob );
                L1World.getInstance().addVisibleObject( mob );
                L1Object l1object = L1World.getInstance().findObject(
                        mob.getId() );
                L1MonsterInstance newnpc = (L1MonsterInstance)l1object;
                newnpc.set_storeDroped( true ); // 召喚されたモンスターはドロップ無し
                if ( summonId == 45061 // カーズドスパルトイ
                        || summonId == 45161 // スパルトイ
                        || summonId == 45181 // スパルトイ
                        || summonId == 45455 )
                { // デッドリースパルトイ
                    newnpc.broadcastPacket( new S_DoActionGFX(
                            newnpc.getId(), ActionCodes.ACTION_Hide ) );
                    newnpc.setStatus( 13 );
                    newnpc.broadcastPacket( new S_NPCPack( newnpc ) );
                    newnpc.broadcastPacket( new S_DoActionGFX(
                            newnpc.getId(), ActionCodes.ACTION_Appear ) );
                    newnpc.setStatus( 0 );
                    newnpc.broadcastPacket( new S_NPCPack( newnpc ) );
                }
                newnpc.onNpcAI();
                newnpc.turnOnOffLight();
                newnpc.startChat( L1NpcInstance.CHAT_TIMING_APPEARANCE ); // チャット開始
            }
        }

        /// <summary>
        /// 現在ChangeTargetで有効な値は2,3のみ
        /// </summary>
        /// <param name="type"></param>
        /// <param name="idx"></param>
        /// <returns></returns>
        private L1Character changeTarget( int type, int idx )
        {
            L1Character target;

            switch ( type )
            {
                case L1MobSkill.CHANGE_TARGET_ME:
                    target = _attacker;
                    break;
                case L1MobSkill.CHANGE_TARGET_RANDOM:
                    // ターゲット候補の選定
                    List<L1Character> targetList = new List<L1Character>();
                    foreach ( L1Object obj in L1World.getInstance().getVisibleObjects(
                            _attacker ) )
                    {
                        if ( obj is L1PcInstance || obj is L1PetInstance
                                || obj is L1SummonInstance )
                        {
                            L1Character cha = (L1Character)obj;

                            int distance = _attacker.getLocation().getTileLineDistance(
                                    cha.getLocation() );

                            // 発動範囲外のキャラクターは対象外
                            if ( !getMobSkillTemplate().isTriggerDistance( idx, distance ) )
                            {
                                continue;
                            }

                            // 障害物がある場合は対象外
                            if ( !_attacker.glanceCheck( cha.getX(), cha.getY() ) )
                            {
                                continue;
                            }

                            if ( !_attacker.getHateList().containsKey( cha ) )
                            { // ヘイトがない場合対象外
                                continue;
                            }

                            if ( cha.isDead() )
                            { // 死んでるキャラクターは対象外
                                continue;
                            }

                            // ゴースト状態は対象外
                            if ( cha is L1PcInstance )
                            {
                                if ( ( (L1PcInstance)cha ).isGhost() )
                                {
                                    continue;
                                }
                            }
                            targetList.Add( (L1Character)obj );
                        }
                    }

                    if ( targetList.Count == 0 )
                    {
                        target = _target;
                    }
                    else
                    {
                        int randomSize = targetList.Count * 100;
                        int targetIndex = _rnd.Next( randomSize ) / 100;
                        target = targetList[targetIndex];
                    }
                    break;

                default:
                    target = _target;
                    break;
            }
            return target;
        }
    }
}
